package com.sinevil.tcpTrans.tcp;

import com.sinevil.tcpTrans.entity.json.BasicJson;
import com.sinevil.tcpTrans.entity.json.FilePacketJson;
import com.sinevil.tcpTrans.entity.json.InstructionJson;
import com.sinevil.tcpTrans.entity.json.handlers.SocketJsonHandle;
import com.sinevil.tcpTrans.entity.task.FilePacketTask;
import com.sinevil.tcpTrans.entity.task.InstructionTask;
import com.sinevil.tcpTrans.entity.task.Task;
import com.sinevil.tcpTrans.entity.task.TaskQueue;
import com.sinevil.tcpTrans.exceptions.ConnectionLostException;
import com.sinevil.tcpTrans.interfaces.Initializable;
import com.sinevil.tcpTrans.utils.JsonReader;
import com.sinevil.tcpTrans.utils.fileTrans.FileTranser;
import com.sinevil.tcpTrans.utils.fileTrans.entity.FileTransInfo;
import lombok.Getter;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;


/**
 * @Classname TCPTransNode
 * @Description TODO
 * @Version 1.0.0
 * @Date 2024/12/21 20:00
 * @Created by sinevil
 */
public abstract class TCPTransNode implements Initializable {
    public static int BUFFER_SIZE = 1024*10;
    protected static final MessageDigest MD5Util;
    protected JsonReader jsonReader = new JsonReader();

    /**
     * 此服务端口号
     */
    @Getter
    protected int Port;
    protected Selector selector;
    @Getter
    protected Thread nodeThread;
    protected SocketJsonHandle socketJsonHandle;
    @Getter
    protected FileTranser fileTranser;

    protected ByteBuffer readBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);

    protected ByteBuffer writeBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);

    /**
     * 用于缓冲待发送的消息
     */
    protected Map<SocketChannel, TaskQueue> writeTaskQueueMap = new HashMap<>();


    @Getter
    protected final Map<String, FileTransInfo> uploadFileInfoMap = new HashMap<>();
    /**
     * 下载文件信息映射
     */
    @Getter
    protected final Map<String, FileTransInfo> downloadFileInfoMap = new HashMap<>();




    static {
        try {
            MD5Util = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    protected TCPTransNode() {
    }


    public abstract void start();

    /**
     * 获取随机端口号
     * @return 端口号
     */
    public static int getRandomPort() {
        return new Random().nextInt(65535 - 1024 + 1) + 1024;
    }


    /**
     * 获取唯一标识符（128位）
     * @return UUID
     */
    public static String getRandomUUID(){
        return UUID.randomUUID().toString();
    }

    public void sendMessage(SocketChannel socketChannel, BasicJson json) {
        if (json instanceof InstructionJson){
            writeTaskQueueMap.get(socketChannel).enqueue(new InstructionTask(json));
        }else if (json instanceof FilePacketJson){
            writeTaskQueueMap.get(socketChannel).enqueue(new FilePacketTask(json));
        }


        // 获取对应的SelectionKey
        SelectionKey key = socketChannel.keyFor(selector);
        if (key!= null) {
            int currentOps = key.interestOps();
            key.interestOps(currentOps | SelectionKey.OP_WRITE);
        }
        // 唤醒selector，让其尽快处理可写事件，避免数据等待太久
        selector.wakeup();
    }

    public int getTaskQueueSize(SocketChannel socketChannel){
        return writeTaskQueueMap.get(socketChannel).size();
    }


    /**
     * 处理可读事件的函数，从SocketChannel读取数据、解析处理JSON字符串等操作
     *
     * @param key 对应的SelectionKey，包含了SocketChannel等信息
     * @throws IOException 如果在处理过程中出现IO相关异常则抛出
     * @throws ConnectionLostException 如果服务器关闭连接则抛出此异常
     */
    protected void handleReadableEvent(SelectionKey key) throws IOException, ConnectionLostException {
        // 处理可读事件，读取接收到的数据并处理
        SocketChannel socketChannel = (SocketChannel) key.channel();

        int bytesRead = socketChannel.read(readBuffer);

        if (bytesRead > 0) {
            readBuffer.flip();
            String str = StandardCharsets.UTF_8.decode(readBuffer).toString();
            LinkedList<BasicJson> jsons = jsonReader.findNextJson(str);
            for (BasicJson json:jsons) {
                socketJsonHandle.handle(socketChannel,json);
            }

        } else if (bytesRead == -1) {
            // 服务器关闭连接，取消对应的SelectionKey并关闭SocketChannel，同时抛出连接丢失异常
            key.cancel();
            socketChannel.close();
            throw new ConnectionLostException("与服务器的连接已断开");
        }
        // 完成一次读取后清空缓冲区，为下一次读取做准备
        readBuffer.clear();
    }

    /**
     * 处理可写事件
     * @param key 事件
     */
    protected void handleWritableEvent(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        TaskQueue writeTaskQueue = writeTaskQueueMap.get(channel);
        while (!writeTaskQueue.isEmpty()) {
            Task task = writeTaskQueue.dequeue();
            ByteBuffer buffer = task.getBytes();
            int bytesWritten = channel.write(buffer);
            if (bytesWritten > 0) {
                if (buffer.hasRemaining()) {
                    // 如果没写完，重新放回队列头部，等待下次可写事件触发时继续发送
                    writeTaskQueue.enqueue(task.renewPriority(32));
                    break;
                }
            } else if (bytesWritten == 0){
                // 通道写入空间已满
                writeTaskQueue.enqueue(task.renewPriority(32));
                break;
            } else {
                // 写入出现问题，将数据放回队列头部，等待后续处理
                writeTaskQueue.enqueue(task.renewPriority(32));
                break;
            }


        }
        // 如果队列不为空，说明还有数据等待发送，OP_WRITE注册
        if (writeTaskQueue.isEmpty()) {
            // 数据全部发送完，取消OP_WRITE注册
            key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
        }
    }

    public void setObjectForBackEvent(Object... objects){
        this.socketJsonHandle.setObjectForBackEvent(objects);
    }

}